/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/app/app.hpp>
#include <baja/graphics.hpp>
#include <baja/graphics/scenes.hpp>
#include <baja/app/input.hpp>
#include <journal/models/interfaces.h>
#include <journal/views/interfaces.h>
#include <journal/common/settings_service.hpp>
#include <journal/views/paper_sheet_node.hpp>
#include <journal/views/page_curl.h>

namespace journal { namespace view {

namespace b = baja;
namespace g = baja::graphics;
namespace gs = baja::graphics::scenes;
namespace bs = baja::storage;

class page : 
    public ipage,
    public ipage_automation,
    public model::ipage_model_observer, 
    public isettings_service_observer, 
    public view::iink_surface_observer, 
    public view::iphoto_observer,
    public std::enable_shared_from_this<ipage>
{
private:

    enum class state
    {
        none,
        loadingThumbnail,
        loadingThumbnailThenFull,
        loadedThumbnail,
        loadingFull,
        loadedFull,
        loadingFullDeferred,
        destructing,
        deletingPhoto,
    };

public:
    page(
        const std::shared_ptr<model::ipage_model>& model,
        const std::shared_ptr<a::iinput_capture>& inputCapture
        );

    ~page();
    static std::shared_ptr<page> create(
        const std::shared_ptr<model::ipage_model>& model,
        const std::shared_ptr<a::iinput_capture>& inputCapture
        );

    // ipage
    std::shared_ptr<model::ipage_model> model() { return _pageModel; }
    std::shared_ptr<iink_surface> inkSurfaceView() { return _inkSurfaceView; }
    std::shared_ptr<gs::iscene_node> sceneNode() { return std::static_pointer_cast<gs::iscene_node>(_pageNode); }
    void setVisible(const bool visible);
    bool visible();
    void setPosition(const m::point3d& position);
    m::point3d position();
    void setRotation(const m::point3d& angles);
    bool selected();
    void setSelected(const bool value);
    m::point3d rotation();
    b::float32 alpha();
    void setAlpha(const b::float32 value);
    void setCurling(const bool value);
    bool curling();
    void setAmbientColor(const g::color& color);
    b::int32 addObserver(const std::shared_ptr<view::ipage_observer>& observer) { return _observerSource += observer; }
    void removeObserver(const std::shared_ptr<view::ipage_observer>& observer) { _observerSource -= observer; }
    void removeObserver(const b::int32 cookie) { _observerSource -= cookie; }
    bool pixelHitTest(const m::point2d& point, m::point3d* localPoint);
    void setLoadLevel(const load_level level);
    void renderShadow(b::float32 x, b::float32 y, b::float32 scaleX);
    bool saveThumbnailAsync();
    bool isThumbnailSaved();
    std::shared_ptr<imemory_blob> generateImage(const m::point2d* dimensions = nullptr);

    void onSelectInkTool(ink_surface_tool tool);
    void onRemoveSelectedPhoto();

    // irender_observer
    void onRender();
    void onResize(const a::resize_args& args) { /* noop */ };
    void onScreenChange(const a::screen_change_args& args) { /* noop */ };
    void onVisibilityChanged(const bool visible) { /* noop */ }

    // icached_render_object
    bool getDynamicBounds(m::rectangle* bounds);
    void renderDynamic();
    bool renderToCache();
    bool shouldRebuildCache();
    void setRebuildCache(const bool value);
    void rebuildRenderData();

    // iinput_observer
    void onPointer(const a::pointer_args& args, void* context); // context is a boolean for handled
    void onGesture(const a::gesture_args& args, void* context = nullptr);
    void onKey(const a::key_args& args, void* context = nullptr);

    // iinput_capture
    void setCapture(const std::shared_ptr<a::iinput_observer>& observer) 
    { 
        _inputCapture.lock()->setCapture(std::static_pointer_cast<a::iinput_observer>(shared_from_this())); 
        _capture.set(observer);
    }

    void releaseCapture()
    { 
        _inputCapture.lock()->releaseCapture();
        _capture.release();
    }

    std::shared_ptr<iinput_observer> getCapture(void** context = nullptr)
    { 
        return _capture.get(); 
    }

    // isettings_service_observer
    void onBeforeSettingChange(const b::wchar* fullName);
    void onAfterSettingChange(const b::wchar* fullName);

    // model::ipage_model_observer
    void onPageInitialized(const std::shared_ptr<model::ipage_model>& page);
    void onPageLoadLevelSet(const std::shared_ptr<model::ipage_model>& page, const model::page_level currentLevel);
    void onPhotoModelAdded(const std::shared_ptr<model::iphoto_model>& photo);
    void onPhotoModelRemoved(const std::shared_ptr<model::iphoto_model>& photo);
    void onPhotoModelBroughtToFront(const std::shared_ptr<model::iphoto_model>& photo);
    void onInkSurfaceModelReady(const std::shared_ptr<model::iink_surface_model>& inkSurface);
    void onPageModelPaperTypeChanged(const b::uint32& paperType);
    void page::onThumbnailSaved(const std::shared_ptr<model::ipage_model>& page);

    // TRACK: 227: figure out a way of bubbling these up w/o so much dupe code
    // view::iphoto_observer
    void onPhotoMoved(
        const std::shared_ptr<view::iphoto>& photo,
        const b::float32& newScale,
        const m::point2d& newPosition,
        const b::float32& newAngle
        )
    {
        // bubble it up
        _observerSource.raise(
            [&](const std::shared_ptr<view::ipage_observer>& observer, void* context)
            {
                observer->onPhotoMoved(photo, newScale, newPosition, newAngle);
            });
    }

    void onPhotoSelected(const std::shared_ptr<view::iphoto>& photo);
    void onPhotoDeselected();
    void iphoto_observer::onDirty() { _shouldRender = true; _isThumbnailFileDirty = _isThumbnailDirty = true; }

    // view::iink_surface_observer
    void onStrokeAdded(const std::shared_ptr<model::iink_stroke_model>& newStroke)
    {
        // bubble it up
        _observerSource.raise(
            [&](const std::shared_ptr<view::ipage_observer>& observer, void* context)
            {
                observer->onStrokeAdded(newStroke);
            });
    }

    void iink_surface_observer::onDirty() { _shouldRender = true; _isThumbnailFileDirty = _isThumbnailDirty = true; }

    // ishare_source
    void onShareImageAsync(copy_info& info, std::function<void(const std::shared_ptr<view::ishared_image>&)> f);
    void onCopyImageAsync(copy_info& info, std::function<void(const std::shared_ptr<view::ishared_image>&)> f);
    void onPasteImageAsync(const std::shared_ptr<view::ishared_image>& image, std::function<void()> f);

    // icommand_observer
    void onCommand(const view::command_id id, void* context);

    // ipage_automation
    void generateFractalInkAsync();
    std::shared_ptr<iink_surface> inkSurface();

private:
    struct photo_holder 
    {
        photo_holder() : observerCookie(-1) {}
        std::shared_ptr<iphoto> photo; 
        b::int32 observerCookie; 
    };

    // thumbnails
    void initThumbnail();
    void setThumbnail(const std::shared_ptr<g::itexture>& texture);
    void releaseThumbnail();
    bool updateThumbnail(const bool force = false);
    
    const std::wstring imageName()
    {
        boost::wformat fmt = boost::wformat(constants::general::attachmentFileName()) % _pageModel->pageNumber();
        return fmt.str();
    }

    // full
    void initFull();
    void releaseFull();

    // other
    void testAnimation(const bool enable);
    const b::wchar* toString(const state s);

    const b::wchar* toString(const load_level l);
    const b::wchar* toString(const model::page_level l);
    std::vector<photo_holder>::iterator findPhoto(const model::id& id);
    void init();
    void cleanup();
    std::shared_ptr<g::itexture> paperTypeToTexture(const view::paper_type paperType);
    std::shared_ptr<g::itexture> missingThumbnailTexture();
    void saveSharedImageAsync(const std::wstring& fileName, std::function<void(const std::shared_ptr<ishared_image>&)> f);
    void completeLoad();
    void addPhotoWithScale(const std::shared_ptr<b::storage::istorage_file>& imageFile);
    void addPhoto(
        const std::shared_ptr<b::storage::istorage_file>& imageFile,
        const b::float32& x,
        const b::float32& y,
        const b::float32& scale,
        const b::float32& angle);

private:
    state _state;
    b::observable_source<view::ipage_observer> _observerSource;
    b::capturable<a::iinput_observer> _capture;
    std::weak_ptr<a::iinput_capture> _inputCapture;
    std::shared_ptr<model::ipage_model> _pageModel;
    b::int32 _inkSurfaceObserverCookie;
    b::int32 _pageObserverCookie;
    b::int32 _settingsObserverCookie;

    std::shared_ptr<paper_sheet_node> _pageNode;
    std::shared_ptr<g::itexture> _thumbnailTexture;

    // data for FULL level of detail
    std::shared_ptr<iink_surface> _inkSurfaceView;
    std::shared_ptr<g::render_target> _cacheRenderTarget;
    std::shared_ptr<g::render_target> _compositeRenderTarget;

    std::vector<photo_holder> _photos;

    // data for THUMBNAIL level of detail
    std::shared_ptr<g::render_target> _thumbnailRenderTarget;

    bool _rebuildCache;
    bool _shouldRender;
    bool _isThumbnailDirty; // dirty flag for thumbnail in memory
    bool _isThumbnailFileDirty;  // dirty flag for thumbnail in disk
    bool _isThumbnailOutOfDate;
    bool _savingThumbnail;

    m::point3d _rotation;
    int _testAnimation;

    b::float32 _alpha;
    bool _selected;

    std::shared_ptr<g::ieffect2> _shadowEffect;

    std::shared_ptr<view::iphoto> _selectedPhoto;

    std::shared_ptr<gs::ibillboard_node> _shadowNode;
    animation _progressAnimation;

    std::recursive_mutex _mutex; // used for deferred rendering

    static b::uint32 _typeToken;
};

std::shared_ptr<ipage> createPage(
    const std::shared_ptr<model::ipage_model>& model,
    const std::shared_ptr<a::iinput_capture>& inputCapture
    );

}} // namespace journal::view
